/**
 * 回顾：js中定义对象的几种方式
 */

/**
 * 方式一：使用对象字面量
 * 优点：快速简单
 * 缺点：没有原型，无法通过prototype添加共享功能，只能描述一个对象结构
 */
 let stu = {
    name:"XiaoMing",
    age:18,
    studey:function(){
        console.log("没事爱敲会儿代码");
    }
 }
//  无法添加共享功能至原型
//  stu.prototype.show=function () {
//     console.log("添加共享方法");
//  }
 let stu2 = {
    name:"XiaoDu",
    age:18,
    studey:function(){
        console.log("没事爱八点小卦");
    }
 }
//  console.log(stu.name,stu.age);

// --------------------------------------------------
/**
 * 方式二：使用构造函数/构造器
 * 优点：基于同一个构造函数创建对象、多个对象具有相同结构，可以添加共享原型属性、方法
 * 缺点：无法为构造函数提供独立的作用域
 */

// var lala = "lala";  //全局作用域，无法只允许我们构造函数访问
// function Studey(name,age){
//     this.name = name;  //this指未来创建出的对象
//     this.age = age;
//     this.myaihao = function(){
//         console.log("我是"+this.name+",我今年"+this.age+",我喜欢。。。");
//         console.log(lala);
//     }
//     // 除了可以在构造函数内添加方法属性，还可以在外部添加原型上共用属性或者方法
// }

// Studey.prototype.myPublicType = "xm";
// Studey.prototype.publicFunction = function(){
//     console.log("我是"+this.name+",我今年"+this.age+",我是原型共用方法");
// }

// let stu3 = new Studey("XiaoMing",188);
// let stu4 = new Studey("XiaoHong",18);
// stu3.myaihao(); //可以调用
// stu4.myaihao();
// stu3.publicFunction();
// console.log(stu4.myPublicType);

/**
 * 构造函数中的属性和方法和原型上的属性和方法的不同
 * 构造函数的属性是每个实例单独的，互不影响，但是原型上的是共有的
 */
// stu3.myPublicType = "xx"; //这里并不是修改了原型属性，而是stu3对象添加了属性
// stu3.__proto__.myPublicType = "xs"; //使用__proto__修改的才是真正的原型属性
// console.log(stu3.myPublicType);
// console.log(stu4.myPublicType);



// --------------------------------------------------

/**
 * 方式三：使用闭包构造函数
 * 使用自执行函数(function (){})(),将构造函数包裹，制造一个独立作用域
 */
let Studey = (function (){
    var lala = "lala";  //全局作用域，无法只允许我们构造函数访问
    function Studey(name,age){
        this.name = name;  //this指未来创建出的对象
        this.age = age;
        this.myaihao = function(){
            console.log("我是"+this.name+",我今年"+this.age+",我喜欢。。。");
        }
        // 除了可以在构造函数内添加方法属性，还可以在外部添加原型上共用属性或者方法
    }

    Studey.prototype.myPublicType = "xm";
    Studey.prototype.publicFunction = function(){
    console.log("我是"+this.name+",我今年"+this.age+",我是原型共用方法");
    console.log(lala);
    }

    return Studey;
})();

let stu5 = new Studey("李一鸣",18);
// stu5.publicFunction();
// console.log(lala); //此时的lala是不可以再被调用的


/**
 * 回顾二：js实现继承的几种方式
 */
function Person(name,age){
    this.name = name;
    this.age = age;
    this.study = function(){
        console.log("正在学习.....");
    }
}
Person.prototype.sex = "xiaoming";  //在父类原型上添加属性
Person.prototype.myobject = function(){
    console.log(this.name,this.age,this.sex);
}//原型上添加属性
/**
 * 方式一：对象冒充继承，也称为构造继承
 * 核心：使用call，以对象冒充的形式调用父类的构造函数
 * 缺陷：只能继承构造函数的属性和方法，无法继承父类原型上的属性和方法
 */
// function Student(name,age){
//     Person.call(this,name,age); //此时person中的this指向Student对象，所以为对象冒充
// }
// let mystu = new Student("XiaoMing",18);
// console.log("继承后的值:"+mystu.name,mystu.age);
// mystu.study();
// mystu.myobject();  //报错找不到方法
// console.log(mystu.sex);//获取不到原型上属性和方法

/**
 * 方式二：原型链继承
 * 核心：使用prototype，将父类的对象作为子类的原型
 * 缺点：创建子类实例时，无法向父类构造函数传递参数，导致集成的父类属性没有值
 */
// function Student(name,age){

// }
// Student.prototype = new Person(); //为子类原型赋值父类的实例化对象，从而集成Person
// var stu1 = new Student("XiaoMing",18);
// stu1.myobject(); //这里发现只能打印出原型上的值，无法给子类构造器参数赋值

/**
 * 方式三：组合继成：对象冒充+原型链
 * 小瑕疵：调用了两次父类构造函数，生成了两份实例（子类实例将子类原型上那份被屏蔽了）
 * 解决方案：子类原型直接指向父类原型
 */
function Student(name,age){
    Person.call(this,name,age); //call指向父类构造函数，改变this指向
}
// Student.prototype = new Person(); //用法一：继承原型链，瑕疵：生成了两份实例，解决：直接指向父类原型
Student.prototype = Person.prototype; //用法二：完美，直接子类的原型继承父类原型
var stu1 = new Student("李一鸣",18);
console.log(stu1.name,stu1.age,stu1.sex); 
stu1.study();
stu1.myobject();